// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using Microsoft.WindowsAPICodePack.DirectX.Direct3D10;
using Microsoft.WindowsAPICodePack.DirectX.Direct3DX10;
using System.Windows.Media.Media3D;


namespace Microsoft.WindowsAPICodePack.DirectX.DirectXUtilities
{
    public static class MatrixExtensions
    {
        #region Matrix3D and Matrix4F extensions
        /// <summary>
        /// Converts a Matrix3D into an array of floats
        /// </summary>
        /// <param name="wpfMatrix">the source matrix</param>
        /// <returns>An array of floats</returns>
        public static float[ ] ToArray( this Matrix3D wpfMatrix )
        {
            return
                new float[ ]
                {
                    (float)wpfMatrix.M11, (float)wpfMatrix.M12, (float)wpfMatrix.M13, (float)wpfMatrix.M14,
                    (float)wpfMatrix.M21, (float)wpfMatrix.M22, (float)wpfMatrix.M23, (float)wpfMatrix.M24,
                    (float)wpfMatrix.M31, (float)wpfMatrix.M32, (float)wpfMatrix.M33, (float)wpfMatrix.M34,
                    (float)wpfMatrix.OffsetX, (float)wpfMatrix.OffsetY, (float)wpfMatrix.OffsetZ, (float)wpfMatrix.M44
                };
        }

        /// <summary>
        /// Converts a Matrix3D to a Matrix4F
        /// </summary>
        /// <param name="wpfMatrix">source matrix</param>
        /// <returns>destination matrix</returns>
        public static Matrix4F ToMatrix4F( this Matrix3D wpfMatrix )
        {
            return new Matrix4F( wpfMatrix.ToArray( ) );
        }

        /// <summary>
        /// Converts a Matrix4F into a Matrix3D
        /// </summary>
        /// <param name="floatMatrix">source matrix</param>
        /// <returns>destination matrix</returns>
        public static Matrix3D ToMatrix3D( this Matrix4F floatMatrix )
        {
            return
                new Matrix3D(
                    floatMatrix._11, floatMatrix._12, floatMatrix._13, floatMatrix._14,
                    floatMatrix._21, floatMatrix._22, floatMatrix._23, floatMatrix._24,
                    floatMatrix._31, floatMatrix._32, floatMatrix._33, floatMatrix._34,
                    floatMatrix._41, floatMatrix._42, floatMatrix._43, floatMatrix._44 );
        }
        #endregion

        #region PerspectiveCamera extensions
        /// <summary>
        /// Returns an array of floats that represent the world*perspective matrix for the camera
        /// </summary>
        /// <param name="perspective">The WPF camera</param>
        /// <param name="aspectRatio">The aspect ratio of the device surface</param>
        /// <returns></returns>
        public static float[ ] ToArrayLH( this PerspectiveCamera perspective, double aspectRatio )
        {
            return perspective.ToMatrix3DLH( aspectRatio ).ToArray( );
        }

        /// <summary>
        /// Returns the world*perspective matrix for the camera
        /// </summary>
        /// <param name="camera">The WPF camera</param>
        /// <param name="aspectRatio">The aspect ratio of the device surface</param>
        /// <returns></returns>
        public static Matrix3D ToMatrix3DLH( this PerspectiveCamera camera, double aspectRatio )
        {
            Transform3DGroup tg = new Transform3DGroup( );
            tg.Children.Add(new MatrixTransform3D(GetLookAtMatrixLH(camera)));
            tg.Children.Add(camera.Transform);
            tg.Children.Add(new MatrixTransform3D(GetPerspectiveMatrixLH(camera, aspectRatio)));
            return tg.Value;
        }

        public static Matrix3D ToViewLH( this PerspectiveCamera camera )
        {
            return GetLookAtMatrixLH( camera );
        }

        public static Matrix3D ToPerspectiveLH( this PerspectiveCamera camera, double aspectRatio )
        {
            return GetPerspectiveMatrixLH( camera, aspectRatio );
        }
        #endregion

        #region PerspectiveCamera implementation
        internal static Matrix3D GetPerspectiveMatrixLH( PerspectiveCamera camera, double aspectRatio )
        {
            double fov = (camera.FieldOfView / 360.0) * 2.0 * Math.PI;
            double zn = camera.NearPlaneDistance;
            double zf = camera.FarPlaneDistance;
            double f = 1.0 / Math.Tan( fov / 2.0 );
            double xScale = f / aspectRatio;
            double yScale = f;
            double n = (1.0 / (zf - zn));
            double m33 = zf * n;
            double m43 = -zf * zn * n;

            return
                new Matrix3D(
                    xScale, 0, 0, 0,
                    0, yScale, 0, 0,
                    0, 0, m33, 1,
                    0, 0, m43, 0 );
        }

        internal static Matrix3D GetLookAtMatrixLH( PerspectiveCamera camera )
        {
            Vector3D f = new Vector3D(
                    camera.Position.X - camera.LookDirection.X,
                    camera.Position.Y - camera.LookDirection.Y,
                    camera.Position.Z - camera.LookDirection.Z );
            f.Normalize();
            Vector3D vUpActual = camera.UpDirection;
            vUpActual.Normalize();
            Vector3D s = Vector3D.CrossProduct( f, vUpActual );
            Vector3D u = Vector3D.CrossProduct( s, f );

            return
                new Matrix3D(
                    s.X, u.X, -f.X, 0,
                    s.Y, u.Y, -f.Y, 0,
                    s.Z, u.Z, -f.Z, 0,
                    -camera.Position.X, -camera.Position.Y, -camera.Position.Z, 1 );
        }
        #endregion
    }
}
